Crate keyring

source ·
Expand description

Keyring

This is a cross-platform library that does storage and retrieval of passwords (and other credential-like secrets) in the underlying platform secure store. A top-level introduction to the library’s usage, as well as a small code sample, may be found in the library’s entry on crates.io. Currently supported platforms are Linux, Windows, MacOS, and iOS.

Design

This module uses platform-native credential managers: secret service on Linux, the Credential Manager on Windows, and the Secure Keychain on Mac and iOS. Each entry constructed with Entry::new(service, username) is mapped to a credential using platform-specific conventions described below.

To facilitate interoperability with third-party software, there are alternate constructors for keyring entries - Entry::new_with_target and Entry::new_with_credential - that use different conventions to map entries to credentials. In addition, the get_password_and_credential method on an entry can be used retrieve the underlying credential data along with the password.

Linux

On Linux, the secret service is used as the platform credential store. Secret service groups credentials into collections, and identifies each credential in a collection using a set of key-value pairs (called attributes). In addition, secret service allows for a label on each credential for use in UI-based clients.

For a given service/username pair, Entry::new maps to a credential in the default (login) secret-service collection. This credential has matching service and username attributes, and an additional application attribute of rust-keyring.

You can map an entry to a non-default secret-service collection by passing the collection’s name as the target parameter to Entry::new_with_target. This module doesn’t ever create collections, so trying to access an entry in a named collection before externally creating and unlocking it will result in a NoStorageAccess error.

If you are running on a headless Linux box, you will need to unlock the Gnome login keyring before you can use it. The following bash function may be very helpful.

function unlock-keyring ()
{
    read -rsp "Password: " pass
    echo -n "$pass" | gnome-keyring-daemon --unlock
    unset pass
}

Trying to access a locked keychain on a headless Linux box often returns the platform error that displays as SS error: prompt dismissed. This refers to the fact that there is no GUI running that can be used to prompt for a keychain unlock.

Windows

There is only one credential store on Windows. Generic credentials in this store are identified by a single string (called the target name). They also have a number of non-identifying but manipulable attributes: a username, a comment, and a target alias.

For a given service/username pair, this module uses the concatenated string username.service as the mapped credential’s target name. (This allows multiple users to store passwords for the same service.) It also fills the username and comment fields with appropriate strings.

Because the Windows credential manager doesn’t support multiple keychains, and because many Windows programs use only the service name as the credential target name, the Entry::new_with_target call uses the target parameter as the credential’s target name rather than concatenating the username and service. So if you have a custom algorithm you want to use for computing the Windows target name (such as just the service name), you can specify the target name directly (along with the usual service and username values).

MacOS and iOS

MacOS/iOS credential stores are called keychains. On iOS there is only one of these, but on Mac the OS automatically creates three of them (or four if removable media is being used). Generic credentials on Mac/iOS can be identified by a large number of key/value attributes; this module (currently) uses only the account and name attributes.

For a given service/username pair, this module uses a generic credential in the User (login) keychain whose account is the username and and whose name is the service. In the Keychain Access UI on Mac, generic credentials created by this module show up in the passwords area (with their where field equal to their name), but Note entries on Mac are also generic credentials and can be accessed by this module if you know their account value (which is not displayed by Keychain Access).

On Mac, you can specify targeting a different keychain by passing the keychain’s (case-insensitive) name as the target parameter to Entry::new_with_target. Any name other than one of the OS-supplied keychains (User, Common, System, and Dynamic) will be mapped to User. On iOS, the target parameter is ignored.

(N.B. The latest versions of the MacOS SDK no longer support creation of file-based keychains, so this module’s experimental support for those has been removed.)

Caveats

This module manipulates passwords as UTF-8 encoded strings, so if a third party has stored an arbitrary byte string then retrieving that password will return an error. The error in that case will have the raw bytes attached, so you can access them.

Accessing the same keychain entry from multiple threads simultaneously can produce odd results, even deadlocks. This is because the system calls to the platform credential managers may use the same thread discipline, and so may be serialized quite differently than the client-side calls. On MacOS, for example, all calls to access the keychain are serialized in an order that is independent of when they are made.

Because credentials identified with empty service, user, or target names are handled inconsistently at the platform layer, the library had inconsistent (and arguably buggy) behavior in this case. As of version 1.2, this inconsistency was eliminated by having the library always fail on access when using credentials created with empty strings via new or new_with_target. The prior platform-specific behavior can still be accessed by using new_with_credential to produce the same credential that would have been produced before the change.

A better way to handle empty strings (and other problematic argument values) would be to allow Entry creation to fail gracefully on arguments that are known not to work on a given platform. That would be a breaking API change, however, so it will have to wait until the next major version.

Re-exports

pub use error::Error;
pub use error::Result;

Modules

Defines the credential model for each supported platform.
Defines a platform-independent error model.

Structs

Functions

return the runtime Platform so cross-platform code can know what kind of credential is in use.